using System;
using System.Collections;
using System.Text;
using System.Diagnostics;

namespace Qiang.Topology.Geometries
{
    /// <summary>
    /// Basic implementation of <c>Point</c>.
    /// </summary>
    public class Point : Geometry ,IPoint
    {
        private static readonly Coordinate emptyCoordinate = null;
        /// <summary>
        /// Represents an empty <c>Point</c>.
        /// </summary>
        public static readonly IPoint Empty = new GeometryFactory().CreatePoint(emptyCoordinate);

        /// <summary>  
        /// The <c>Coordinate</c> wrapped by this <c>Point</c>.
        /// </summary>
        private ICoordinateSequence coordinates;

        public virtual ICoordinateSequence CoordinateSequence
        {
            get
            {
                return coordinates;
            }
        }

        /// <summary>
        /// Constructs a <c>Point</c> with the given coordinate.
        /// </summary>
        /// <param name="coordinate">
        /// The coordinate on which to base this <c>Point</c>
        /// , or <c>null</c> to create the empty point.
        /// </param>
        /// <param name="precisionModel">
        /// The specification of the grid of allowable points
        /// for this <c>Point</c>.
        /// </param>
        /// <param name="SRID">
        /// The ID of the Spatial Reference System used by this
        /// <c>Point</c>
        /// </param>
        /// <deprecated>
        /// Use GeometryFactory instead.
        /// </deprecated>
        [Obsolete("Use GeometryFactory instead")]
        public Point(Coordinate coordinate, PrecisionModel precisionModel, int SRID)
            : this(DefaultCoordinateSequenceFactory.Instance.Create(
                coordinate != null ? new Coordinate[] { coordinate } : new Coordinate[] { }), 
                new GeometryFactory(precisionModel, SRID, DefaultCoordinateSequenceFactory.Instance)) { }

        /// <param name="coordinates">
        /// Contains the single coordinate on which to base this <c>Point</c>
        /// , or <c>null</c> to create the empty point.
        /// </param>
        public Point(ICoordinateSequence coordinates, GeometryFactory factory) : base(factory)
        {               
            if (coordinates == null) 
                coordinates = factory.CoordinateSequenceFactory.Create(new Coordinate[] { });
            Debug.Assert(coordinates.Count <= 1);
            this.coordinates = coordinates;
        }


        public Point(Coordinate coordinate)
            : this(new DefaultCoordinateSequence(new Coordinate[1] { coordinate }), DefaultGeometryFactory)
        { }


        public override Coordinate[] Coordinates 
        {
            get
            {
                return IsEmpty ? new Coordinate[] { } : new Coordinate[]{ this.Coordinate };
            }
        }

        public override int NumPoints
        {
            get
            {
                return IsEmpty ? 0 : 1;
            }
        }

        public override bool IsEmpty 
        {
            get
            {
                return this.Coordinate == null;
            }
        }

        public override bool IsSimple
        {
            get
            {
                return true;
            }
        }

        public override bool IsValid
        {
            get
            {
                return true;
            }
        }

        public override Dimensions Dimension
        {
            get
            {
                return Dimensions.Point;
            }
        }

        public override Dimensions BoundaryDimension 
        {
            get
            {
                return Dimensions.False;
            }
        }

        public virtual double X
        {
            get
            {
                if (Coordinate == null)
                    throw new ArgumentOutOfRangeException("X called on empty Point");                
                return Coordinate.X;
            }
        }

        public virtual double Y 
        {
            get
            {
                if (Coordinate == null)
                    throw new ArgumentOutOfRangeException("Y called on empty Point");                
                return Coordinate.Y;
            }
        }

        public override Coordinate Coordinate
        {
            get
            {
                return coordinates.Count != 0 ? coordinates.GetCoordinate(0) : null;
            }
        }

        [Obsolete("Use reflection! GetType().FullName")]
        public override string GeometryType 
        {
            get
            {
                return "Point";
            }
        }

        public override IGeometry Boundary
        {
            get
            {
                return Factory.CreateGeometryCollection(null);
            }
        }

        protected override IEnvelope ComputeEnvelopeInternal() 
        {
            if (IsEmpty) 
                return new Envelope();            
            return new Envelope(Coordinate.X, Coordinate.X, Coordinate.Y, Coordinate.Y);
        }

        public override bool EqualsExact(IGeometry other, double tolerance) 
        {
            if (!IsEquivalentClass(other)) 
                return false;            
            if (IsEmpty && other.IsEmpty) 
                return true;
            return Equal(((Point)other).Coordinate, this.Coordinate, tolerance);
        }

        public override void Apply(ICoordinateFilter filter) 
        {
            if (IsEmpty) 
                return;             
            filter.Filter(Coordinate);
        }

        public override void Apply(IGeometryFilter filter)
        {
            filter.Filter(this);
        }

        public override void Apply(IGeometryComponentFilter filter) 
        {
            filter.Filter(this);
        }

        public override object Clone() 
        {
            Point p = (Point) base.Clone();
            p.coordinates = (ICoordinateSequence)coordinates.Clone();
            return p; 
        }

        public override void Normalize() { }

        protected internal override int CompareToSameClass(object other) 
        {
            Point point = (Point) other;
            return Coordinate.CompareTo(point.Coordinate);
        }        
    }
}
